use darling::FromDeriveInput;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use proc_macro2::TokenStream as T2;
use syn::{parse_macro_input, DeriveInput, Ident};

use crate::periph::{rcc_enable, RccAxB};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(dma_config))]
struct Attr {
    dma: u8,
    ch: u8,
    size: u8,
    dir: String,
}

// p_to_m, m_to_p

impl Attr {

    fn init(&self, init_fn: &Ident, start_fn: &Ident) -> T2 {
        let dma= format_ident!("DMA{}", self.dma);
        let ch = format_ident!("ch{}", self.ch);
        let cgif = format_ident!("cgif{}", self.ch);
        let ctcif = format_ident!("ctcif{}", self.ch);
        let chtif = format_ident!("chtif{}", self.ch);
        let cteif = format_ident!("cteif{}", self.ch);
        let rcc = format_ident!("dma{}en", self.dma);
        let cr = match self.dir.as_ref() {
            "p_to_m" => self.p_to_m(),
            "m_to_p" => self.m_to_p(),
            _ => panic!("invalid dir"),
        };
        let ext = match self.dir.as_ref() {
            "p_to_m" => quote! {},
            "m_to_p" => self.m_to_p_ext(),
            _ => panic!("invalid dir"),
        };
        let rcc_src = rcc_enable(RccAxB::AHB, &rcc, false);
        quote! {
            fn #init_fn() {
                #rcc_src
            }

            fn #start_fn(mar: u32, par: u32, count: u16) {
                use stm32f1::stm32f103::#dma;
                let dma: #dma = unsafe {
                    ::core::mem::transmute(())
                };
                dma.#ch.cr.reset();
                dma.ifcr.write(|w| {
                    w.#cgif().clear()
                    .#ctcif().clear()
                    .#chtif().clear()
                    .#cteif().clear()
                });
                dma.#ch.mar.write(|w| w.ma().variant(mar));
                dma.#ch.par.write(|w| w.pa().variant(par));
                dma.#ch.ndtr.write(|w| w.ndt().variant(count));
                #cr
            }
            #ext
        }
    }

    fn m_to_p_ext(&self) -> T2 {
        let dma= format_ident!("DMA{}", self.dma);
        let cgif = format_ident!("cgif{}", self.ch);
        let ctcif = format_ident!("ctcif{}", self.ch);
        let chtif = format_ident!("chtif{}", self.ch);
        let cteif = format_ident!("cteif{}", self.ch);
        let tcif = format_ident!("tcif{}", self.ch);
        let clear_flags = format_ident!("clear_flags_with_dam{}_ch{}", self.dma, self.ch);
        let is_tc = format_ident!("is_tc_with_dma{}_ch{}", self.dma, self.ch);
        quote! {

            fn #clear_flags() {
                use stm32f1::stm32f103::#dma;
                let dma: #dma = unsafe {
                    ::core::mem::transmute(())
                };
                dma.ifcr.write(|w| {
                    w.#cgif().clear()
                    .#ctcif().clear()
                    .#chtif().clear()
                    .#cteif().clear()
                });
            }

            fn #is_tc() -> bool {
                use stm32f1::stm32f103::#dma;
                let dma: #dma = unsafe {
                    ::core::mem::transmute(())
                };
                dma.isr.read().#tcif().is_complete()
            }
        }
    }

    fn m_to_p(&self) -> T2 {
        let ch = format_ident!("ch{}", self.ch);
        let size = format_ident!("bits{}", self.size);
        quote! {
            dma.#ch.cr.write(|w| {
                w.mem2mem().disabled()
                .pl().low()
                .msize().#size()
                .psize().#size()
                .minc().enabled()
                .pinc().disabled()
                .circ().disabled()
                .dir().from_memory()
                .teie().disabled()
                .htie().disabled()
                .tcie().enabled()
                .en().enabled()
            });
        }
    }

    fn p_to_m(&self) -> T2 {
        let ch = format_ident!("ch{}", self.ch);
        let size = format_ident!("bits{}", self.size);
        quote! {
            dma.#ch.cr.write(|w| {
                w.mem2mem().disabled()
                .pl().low()
                .msize().#size()
                .psize().#size()
                .minc().enabled()
                .pinc().disabled()
                .circ().enabled()
                .dir().from_peripheral()
                .teie().disabled()
                .htie().disabled()
                .tcie().disabled()
                .en().enabled()
            });
        }
    }
}

pub fn create_dma(dma: u8, ch: u8, size: u8, dir: String, init_fn: &Ident, start_fn: &Ident) -> T2 {
    let attr = Attr {
        dma, ch, size, dir
    };
    attr.init(init_fn, start_fn)
}

pub fn build(input: TokenStream) -> TokenStream {

    let input = parse_macro_input!(input as DeriveInput);
    let attr = Attr::from_derive_input(&input).unwrap();
    let init_fn = format_ident!("dma_init");
    let start_fn = format_ident!("dma_start");
    let fn_impl = attr.init(&init_fn, &start_fn);
    let name = &input.ident;

    let expand = quote! {
        impl #name {
            #fn_impl
        }
    };
    
    expand.into()
}
